import { NextApiRequest, NextApiResponse } from "next";
import { initORM } from "../../../utils/orm";
import { Endpoint } from "../../../entities/Endpoint";
import { Dataset } from "../../../entities/Dataset";
import { useRouter } from "next/router";
import { getNow, pickData } from "@/utils/base";
import { writeToStream } from "fast-csv";
import { App } from "@/entities/App";

async function getMockData(req: NextApiRequest, res: NextApiResponse) {
  const { segs: segList } = req.query;

  //  for type infer only, actually no this case
  if (!Array.isArray(segList) || segList.length < 3) {
    res.status(400).json({
      success: false,
      message: "Invalid API, eg: /api/mock/{APP_NAME}/{DATASET_NAME}/user/info",
      data: {},
    });
    return;
  }

  const [appName, datasetName, ...segs] = segList;
  const endpointValue = "/" + segs.join("/");

  const reqData = { ...req.query, ...(req.body || {}) };
  const method = (req.method ?? "GET").toLowerCase();
  const idkey = (reqData.idkey as string) || "id";
  const idval = reqData[idkey];
  const hasIdParam = idval != undefined;

  const em = await initORM();
  const endpointRecords = await em.find(
    Endpoint,
    { value: endpointValue },
    { populate: ["dataset", "dataset.app"] }
  );
  const endpointRows = endpointRecords.filter((endpoint) => {
    const dataset = endpoint.dataset as unknown as Dataset;
    const app = dataset.app as unknown as App;
    return dataset.name === datasetName && app.name === appName;
  });

  if (!endpointRows.length) {
    res.status(400).json({
      success: false,
      message: "Endpoint not found",
      data: {},
    });
    return;
  }

  const chooseSuitableEndpoint = () => {
    if (endpointRows.length === 1) {
      return endpointRows[0];
    }
    const endpointTypes = {
      get: hasIdParam ? ["get"] : ["query"],
      post: hasIdParam ? ["update"] : ["create", "update"],
      put: ["update"],
      delete: ["delete"],
    };
    const epTypes = endpointTypes[method as "get" | "post" | "put" | "delete"];
    const methodMatched = endpointRows.filter((ep) =>
      epTypes.includes(ep.type)
    );
    if (methodMatched.length === 1) {
      return methodMatched[0];
    }

    const getBetterEndpoint = (endpointList: Endpoint[]) => {
      const defaultIdEndpoint = endpointList.find((ep) => {
        const dataset = ep.dataset as unknown as Dataset;
        const defaultId = dataset.defValueId;
        return !!defaultId;
      });

      return defaultIdEndpoint || endpointList[0];
    };
    if (methodMatched.length > 1) {
      return getBetterEndpoint(methodMatched);
    }

    if (methodMatched.length === 0) {
      return getBetterEndpoint(endpointRows);
    }
  };

  // const endpoint = await em.findOne(Endpoint, { value: endpointValue }, { populate: ["dataset"] });
  const endpoint = chooseSuitableEndpoint();

  if (!endpoint) {
    res.status(400).json({
      success: false,
      message: "Endpoint not found",
      data: {},
    });
    return;
  }

  const dataset = endpoint.dataset as unknown as Dataset;
  const defaultId = dataset.defValueId;
  const hasDefaultId = defaultId != null && defaultId + "" !== "";
  const dataList = JSON.parse(dataset.value);
  const id = idval ?? defaultId;
  const idListRe = /,|\||;/;
  const idList = (id + "")
    .split(idListRe)
    .filter((v: any) => !!v)
    .map((v) => Number(v));

  const getEndpointType = (method: string, reqData: any) => {
    const idkey = (reqData.idkey as string) || "id";
    const id = Number(reqData[idkey]);
    if (method === "get") {
      return id ? "get" : "query";
    } else if (method === "put" && id) {
      return "update";
    } else if (method === "delete" && id) {
      return "delete";
    } else if (method === "post") {
      return !id ? "create" : "update";
    }

    res.status(400).json({
      success: false,
      message: "RESTful API, but parameters is invalid",
      data: {
        endpoints: endpointRows.map((ep) =>
          pickData(ep, ["id", "value", "type"])
        ),
      },
    });

    return false;
  };

  // console.log("🚀 ~ getMockData ~ endpointRows.length:", endpointRows.length);

  // support RESTful API
  // const endpointType =
  //   endpointRows.length === 1
  //     ? endpoint.type
  //     : getEndpointType(method, reqData);
  const endpointType = endpoint.type;
  const isOutputCsv = endpoint.isOutputCsv * 1 > 0;

  if (!endpointType) return;

  // console.log("🚀 ~ getMockData ~ endpointType:", endpointType);

  const updateDataset = async (dataList: any[]) => {
    const datasetEntity = await em.findOne(Dataset, { id: dataset.id });
    if (!datasetEntity) {
      res.status(400).json({
        success: false,
        message: "Dataset not found",
        data: {},
      });
      return false;
    }

    datasetEntity.value = JSON.stringify(dataList);

    await em.persistAndFlush(datasetEntity);

    return true;
  };

  const getNewData = (data: Obj) => {
    const newData = { ...data };
    delete newData.segs;
    delete newData.idkey;

    return newData;
  };

  if (endpointType === "get") {
    if (!id) {
      res.status(400).json({
        success: false,
        message: "Missing id",
        data: {},
      });
      return;
    }

    const data = dataList.find(
      (item: any) => Number(item[idkey]) === Number(id)
    );

    if (!data) {
      return res.status(400).json({
        success: false,
        message: "Data not found",
        data: {
          dataList,
          id,
        },
      });
    }
    return res.status(200).json({
      success: true,
      message: "get success",
      data: hasIdParam ? data : data.data,
    });
  } else if (endpointType === "query") {
    if (!isOutputCsv) {
      res
        .status(200)
        .json({ success: true, message: "query success", data: dataList });
    } else {
      const ids = reqData.ids ?? "";
      const idList = ids.split(",").filter((v: any) => !!v);
      const outDataList = idList.length
        ? dataList.filter(
            (item: any) =>
              idList.includes(item[idkey]) || idList.includes(item[idkey] + "")
          )
        : dataList;
      res.setHeader("Content-Type", "application/octet-stream");
      res.setHeader(
        "Content-Disposition",
        `attachment; filename=${(reqData.filename ?? "") + getNow()}.csv`
      );
      res.status(200);
      writeToStream(res, outDataList, { headers: true });
    }
  } else if (endpointType === "create") {
    const newId =
      Math.max(...dataList.map((item: any) => Number(item[idkey]))) + 1;
    reqData[idkey] = newId;

    const newData = getNewData(reqData);

    dataList.push(newData);
    const ok = await updateDataset([...dataList]);
    if (ok) {
      return res.status(200).json({
        success: true,
        message: "create success",
        data: { id: newId },
      });
    }
  } else if (endpointType === "update") {
    if (!id) {
      return res.status(400).json({
        success: false,
        message: "Missing id",
        data: {},
      });
    }

    const isSingleUpdate = idList.length === 1;
    // single update
    if (isSingleUpdate) {
      const data = !hasDefaultId
        ? dataList.find((item: any) => Number(item[idkey]) === Number(id))
        : dataList.find((item: any) => Number(item.id) === Number(defaultId));

      if (!data) {
        return res.status(400).json({
          success: false,
          message: "Data not found",
          data: {},
        });
      }

      const newData = getNewData(reqData);

      if (!hasDefaultId) {
        Object.assign(data, newData, { [idkey]: id * 1 });
      } else {
        Object.assign(data, { data: newData }, { [idkey]: id * 1 });
      }
    } else {
      // batch update
      const matchedList = dataList.filter((item: any) =>
        idList.includes(item[idkey])
      );
      if (!matchedList.length) {
        return res.status(400).json({
          success: false,
          message: "All data not found",
          data: {},
        });
      }

      matchedList.forEach((item: any) => {
        Object.assign(item, getNewData(reqData), { [idkey]: item[idkey] }); // can not change id value
      });
    }

    const ok = await updateDataset([...dataList]);
    if (ok) {
      return res
        .status(200)
        .json({ success: true, message: "update success!", data: { id: id } });
    }
  } else if (endpointType === "delete") {
    if (!id) {
      return res.status(400).json({
        success: false,
        message: "Missing id",
        data: {},
      });
    }

    const newDataList = dataList.filter(
      (item: any) => !idList.includes(Number(item[idkey]))
    );

    if (newDataList.length === dataList.length) {
      return res.status(400).json({
        success: false,
        message: `Can not found data by "${id}"`,
        data: {},
      });
    }

    const ok = await updateDataset([...newDataList]);
    if (ok) {
      return res
        .status(200)
        .json({ success: true, message: "delete success", data: { id: id } });
    }
  }
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  getMockData(req, res);
}
